package cn.jtcoding.web.framework.aspect;

import cn.jtcoding.common.framework.enums.ApiResultCode;
import cn.jtcoding.common.framework.exception.ApiException;
import cn.jtcoding.common.framework.jackson.JacksonSerializer;
import cn.jtcoding.web.framework.annotation.Idempotent;
import cn.jtcoding.web.framework.annotation.IdempotentParam;
import com.google.common.base.Joiner;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author jason.tang
 * @date 2021/4/9 9:48
 * @desc API 保证幂等性切面
 */
@Slf4j
@Aspect
@Component
public class IdempotentAspect {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private HttpServletRequest request;

    @Pointcut("execution(public * cn.jtcoding..*.controller..*.*(..)) && @annotation(cn.jtcoding.web.framework.annotation.Idempotent)")
    public void rule() {
    }

    @Around("rule()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        final String lockKey = this.getLockKey(pjp);
        String sessionId = request.getSession().getId();
        String resultKey = idempotent.prefix() + DigestUtils.md5DigestAsHex(lockKey.getBytes(StandardCharsets.UTF_8)) + idempotent.delimiter() + sessionId;
        RLock lock = redissonClient.getLock(resultKey);
        log.debug("lock: {}, resultKey: {}", lock, resultKey);
        if (lock.isLocked()) {
            log.debug("未获取到锁...");
            throw new ApiException(ApiResultCode.GET_LOCK_FAIL);
        }
        lock.lock(idempotent.period(), TimeUnit.SECONDS);
        log.debug("已获取到锁..");
        return pjp.proceed();
    }

    private String getLockKey(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        final Object[] args = pjp.getArgs();
        final Parameter[] parameters = method.getParameters();
        StringBuilder builder = new StringBuilder();
        for (int i = 0, length = parameters.length; i < length; i++) {
            final Object object = args[i];
            IdempotentParam idempotentParam = parameters[i].getAnnotation(IdempotentParam.class);
            // 默认解析方法里面带 IdempotentParam 注解的属性,如果没有尝试着解析实体对象中的Field
            if (idempotentParam == null) {
                final Field[] fields = object.getClass().getDeclaredFields();
                String result = parseObjectField(fields, idempotent.delimiter());
                builder.append(idempotent.delimiter()).append(result);
                continue;
            }
            String result = exclude(object, idempotentParam.excludeFields());
            builder.append(idempotent.delimiter()).append(result);
        }
        return builder.toString();
    }

    /**
     * 解析对象的Field
     */
    private String parseObjectField(Field[] fields, String delimiter) {
        StringBuilder builder = new StringBuilder();
        for (Field field : fields) {
            IdempotentParam idempotentParam = field.getAnnotation(IdempotentParam.class);
            if (idempotentParam == null) {
                continue;
            }
            String jsonObj = exclude(field, idempotentParam.excludeFields());
            builder.append(delimiter).append(jsonObj);
        }
        return builder.toString();
    }

    /**
     * 对 object 进行过滤
     */
    private String exclude(Object object, String[] excludeFields) {
        String jsonObj = JacksonSerializer.toJSONString(object);
        if (excludeFields.length > 0 && jsonObj != null) {
            String regex = regexBuilder(excludeFields);
            jsonObj = jsonObj.replaceAll(regex, "");
        }
        return jsonObj;
    }

    /**
     * 构建过滤的正则表达式, 处理 JSON 格式的 String、Number key-value
     * Example: ((?<="username":")[^",]*)
     */
    private String regexBuilder(String[] fields) {
		if (fields == null || fields.length == 0) {
			return "";
		}
        List<String> regexList = new ArrayList<>();
        for (String regex : fields) {
            String regexBuilder = "((?<=\"" + regex + "\":\")[^\",]*)" + // json格式的String(包括日期格式化字符串)
                    "|((?<=\"" + regex + "\":)[0-9]*)";// json格式的number
            regexList.add(regexBuilder);
        }
        return Joiner.on("|").join(regexList);
    }
}
